package com.stateofflow.invariantj;

import java.util.Collection;
import java.util.Set;
import java.util.Stack;
import java.util.TreeSet;

public class ThreadState {
    private static ThreadLocal state = new ThreadLocal() {
        protected Object initialValue() {
            return new ThreadState();
        }
    };
    
    private Set failedInvariants = new TreeSet();
    private NamedThrowableMap invariantExceptions = new NamedThrowableMap();
    private int invariantCheckDepth;
    
    public static void addFailedInvariant(String invariantName) {
        get().failedInvariants.add(invariantName);
    }
    
    public static void addExceptionInInvariantCheck(String invariantName, Throwable t) {
        get().invariantExceptions.put(invariantName, t);
    }
    
    public static void beginInvariantCheck() {
        get().invariantCheckDepth++;
    }
    
    public static void endInvariantCheck(Throwable thrownInCheckedMethod) {
        ThreadState threadState = get();
        threadState.invariantCheckDepth--;
        
        if (threadState.invariantCheckDepth > 0) {
            return;
        }
        
        Collection failures = threadState.failedInvariants;
        NamedThrowableMap exceptionsMap = threadState.invariantExceptions;
        if (!failures.isEmpty() || !exceptionsMap.isEmpty()) {
            failInvariantCheck(thrownInCheckedMethod, threadState, failures, exceptionsMap);
        }
    }

    private static void failInvariantCheck(Throwable thrownInCheckedMethod, ThreadState threadState, Collection failures, NamedThrowableMap exceptionsMap) throws InvariantViolationError {
        String[] failuresArray = (String[]) failures.toArray(new String[failures.size()]);
        failures.clear();
        threadState.invariantExceptions = new NamedThrowableMap();
        throw new InvariantViolationError(failuresArray, exceptionsMap, thrownInCheckedMethod);
    }
    
    public static void enter(Object entering) {
        getObjectStack().push(entering);
    }
    
    public static void exit() {
        getObjectStack().pop();
    }
    
    public static boolean isInFlow(Object obj) {
        return getObjectStack().contains(obj);
    }

    public static void clear() {
        get().failedInvariants.clear();
        get().invariantCheckDepth = 0;
    }
    
    private static Stack getObjectStack() {
        return get().objectStack;
    }

    private static ThreadState get() {
        return ((ThreadState) state.get());
    }
    
    private Stack objectStack = new Stack();
}
